package index.btree;

import static storage.file.Page.BLOCK_SIZE;
import static storage.file.Page.INT_SIZE;
import static storage.sql.Types.INTEGER;

import compiler.absyn.Constant;
import compiler.absyn.IntConstant;
import compiler.absyn.StringConstant;

import storage.file.Block;
import storage.record.RID;
import storage.record.Schema;
import storage.record.TableInfo;
import storage.tx.Transaction;

public class BTreePage {
	private Block currentblk;
	private TableInfo ti;
	private Transaction tx;
	private int slotsize;

	public BTreePage(Block blk, TableInfo ti, Transaction tx) {
		this.currentblk = blk;
		this.ti = ti;
		this.tx = tx;
		slotsize = ti.recordLength();
		tx.pin(currentblk);
	}

	public int findSlotBefore(Constant searchkey) {
		int slot = 0;
		while (slot < getNumRecs() && getDataVal(slot).compareTo(searchkey) < 0)
			slot++;
		return slot - 1;
	}

	public void close() {
		if (currentblk != null)
			tx.unpin(currentblk);
		currentblk = null;
	}

	public boolean isFull() {
		return slotpos(getNumRecs() + 1) >= BLOCK_SIZE;
	}

	public Constant split(BTreePage dest) {
		int splitslot = getNumRecs() / 2;
		Constant splitval = getDataVal(splitslot);
		if (!splitval.equals(getDataVal(0))) {
			while (splitval.equals(getDataVal(splitslot - 1)))
				splitslot--;
		}
		transferRecs(splitslot, dest);
		return splitval;
	}

	public Constant getDataVal(int slot) {
		return getVal(slot, "dataval");
	}

	public int getFlag() {
		return tx.getInt(currentblk, 0);
	}

	public void setFlag(int val) {
		tx.setInt(currentblk, 0, val);
	}

	public Block appendNew(String filename, int flag) {
		return tx.append(filename, new BTPageFormatter(ti, flag));
	}

	public int getChildNum(int slot) {
		return getInt(slot, "block");
	}

	public void insertDir(int slot, Constant val, int blknum) {
		insert(slot);
		setVal(slot, "dataval", val);
		setInt(slot, "block", blknum);
	}

	public void moveTo(Block blk) {
		BTreePage newpage = new BTreePage(blk, ti, tx);
		transferRecs(0, newpage);
		newpage.close();
	}

	public RID getDataRid(int slot) {
		return new RID(getInt(slot, "block"), getInt(slot, "id"));
	}

	public void insertLeaf(int slot, Constant val, RID rid) {
		insert(slot);
		setVal(slot, "dataval", val);
		setInt(slot, "block", rid.blocknum());
		setInt(slot, "id", rid.id());
	}

	public void delete(int slot) {
		for (int i = slot + 1; i < getNumRecs(); i++) {
			copyRecord(i, i - 1);
		}
		setNumRecs(getNumRecs() - 1);
		return;
	}

	public int getNumRecs() {
		return tx.getInt(currentblk, INT_SIZE);
	}

	private int getInt(int slot, String fldname) {
		int pos = fldpos(slot, fldname);
		return tx.getInt(currentblk, pos);
	}

	private String getString(int slot, String fldname) {
		int pos = fldpos(slot, fldname);
		return tx.getString(currentblk, pos);
	}

	private Constant getVal(int slot, String fldname) {
		int type = ti.schema().type(fldname);
		if (type == INTEGER)
			return new IntConstant(getInt(slot, fldname));
		else
			return new StringConstant(getString(slot, fldname));
	}

	private void setInt(int slot, String fldname, int val) {
		int pos = fldpos(slot, fldname);
		tx.setInt(currentblk, pos, val);
	}

	private void setString(int slot, String fldname, String val) {
		int pos = fldpos(slot, fldname);
		tx.setString(currentblk, pos, val);
	}

	private void setVal(int slot, String fldname, Constant val) {
		int type = ti.schema().type(fldname);
		if (type == INTEGER)
			setInt(slot, fldname, (Integer) val.asJavaVal());
		else
			setString(slot, fldname, (String) val.asJavaVal());
	}

	private void setNumRecs(int n) {
		tx.setInt(currentblk, INT_SIZE, n);
	}

	private void insert(int slot) {
		for (int i = getNumRecs(); i > slot; i--) {
			copyRecord(i - 1, i);
		}
		setNumRecs(getNumRecs() + 1);
	}

	private void copyRecord(int from, int to) {
		Schema sch = new Schema();
		for (String fldname : sch.fields()) {
			setVal(to, fldname, getVal(from, fldname));
		}
	}

	private void transferRecs(int slot, BTreePage dest) {
		int destslot = 0;
		while (slot < getNumRecs()) {
			dest.insert(destslot);
			Schema sch = ti.schema();
			for (String fldname : sch.fields()) {
				dest.setVal(destslot, fldname, getVal(slot, fldname));
			}
			delete(slot);
			destslot++;
		}
	}

	private int fldpos(int slot, String fldname) {
		int offset = ti.offset(fldname);
		return slotpos(slot) + offset;
	}

	private int slotpos(int slot) {
		return INT_SIZE + INT_SIZE + (slot * slotsize);
	}
}
